package com.atguigu.nio;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

public class NIOServer {
    static final Logger LOGGER = LoggerFactory.getLogger(NIOServer.class);

    public static void main(String[] args) throws IOException {
        int port = 9090;
        try {
            //1. 获取通道
            ServerSocketChannel ssChannel = ServerSocketChannel.open();
            //设置为非阻塞才能注册到选择器。FileChannel不能切换到非阻塞模式
            ssChannel.configureBlocking(false);

            //2. 绑定端口
            ssChannel.bind(new InetSocketAddress(port));

            //4.  将通道注册到选择器上
            Selector selector = Selector.open();
            ssChannel.register(selector, SelectionKey.OP_ACCEPT);

            LOGGER.info("NIO服务已启动，绑定端口号为：" + port);
            //5. 通过轮询的方式，获取准备就绪的事件
            while (selector.select() > 0) {   //该方法会一直阻塞，直到至少有一个 SelectionKey 准备就绪

                //6. 获取当前选择器中所有注册的 SelectionKey
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {

                    SelectionKey key = iterator.next();

                    if (key.isAcceptable()) {
                        handleAccept(key);
                    } else if (key.isReadable()) {
                        handleRead1(key);
                    }

                    //当SelectionKey 任务完成后需要移除，否则会一直执行这个key。
                    iterator.remove();
                }
            }
            ssChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void handleAccept(SelectionKey key) {
        try {
            LOGGER.info("接收就绪！");

            //7. 获取接受状态准备就绪的 selectionKey
            //调用accept 方法获取通道
            SocketChannel sChannel = ((ServerSocketChannel) key.channel()).accept();

            Selector selector = key.selector();
            //8. 将 sChannel 设置为非阻塞的
            sChannel.configureBlocking(false);
            //9. 将该通道注册到选择器上，让选择器能够监听这个通道
            sChannel.register(selector, SelectionKey.OP_READ);
            LOGGER.info("当前接收的连接: " + sChannel);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void handleRead(SelectionKey key) throws IOException {
        LOGGER.info("读取就绪！");
        //10. 获取 读状态 准备就绪的 selectionKey
        SocketChannel sChannel = (SocketChannel) key.channel();
        //创建缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1032);
        byte[] bytes = new byte[1024];

        int read = 0;
        StringBuilder receiveMessage = new StringBuilder();
        int temp;
        boolean statusSent = false;

        //从通道中循环读取数据写入缓冲区，直到达到流的末尾，也就是客户端主动关闭输出流（SocketChannel.shutdownOutput()），或者关闭通道。但由于稍后客户端还需要从服务端接收数据，因此客户端还不能关闭通道。
        while ((read = sChannel.read(buf)) != -1) {
            //该方法会让缓冲区切换到读取状态，即①ByteBuff.limit = ByteBuff.position; ②ByteBuff.position = 0;
            buf.flip();
            receiveMessage.append(StandardCharsets.UTF_8.newDecoder().decode(buf));
            //该方法让缓冲区清空，并准备下一次写入，即①ByteBuff.limit = ByteBuff.capacity; ②ByteBuff.position = 0;
            buf.clear();
        }
        //执行至此时，地址接收完毕
        String tpUrl = receiveMessage.toString();
        LOGGER.info("长度：[{}]，完整url: [{}]", tpUrl.length(), tpUrl);

        URL url = new URL(tpUrl);
        InputStream inputStream = null;
        int responseCode = -1;
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setConnectTimeout(10000);
        try {
            inputStream = con.getInputStream();
            responseCode = con.getResponseCode();
        } catch (IOException e) {
            String format = String.format("图片资源不存在或网络不通！图片地址：%s，异常信息：%s，状态码：%d", tpUrl, e.getMessage(), responseCode);
            e.printStackTrace();
            LOGGER.error(format);
            //返回异常信息。我在这里自定义了服务器返回的第一个Int类型数据标识着当前请求成功与否1：成功，0：失败。
            buf.putInt(0);
            buf.put(format.getBytes(StandardCharsets.UTF_8));
            buf.flip();
            sChannel.write(buf);
            buf.clear();
            if (inputStream != null) {
                inputStream.close();
            }
            //关闭通道，告知客户端写入已达到末尾，让它不再需要等待服务端。
            sChannel.close();
            return;
        }

        while ((temp = inputStream.read(bytes)) != -1) {
            if (!statusSent) {
                //发送成功标识
                buf.putInt(1);
                statusSent = true;
            }
            //写入图片数据到缓冲区
            buf.put(bytes, 0, temp);
            //反转一下，准备读取
            buf.flip();
            sChannel.write(buf);
            //清空缓冲区，准备下一次写入
            buf.clear();
        }

        LOGGER.info("返回结束.");
        if (inputStream != null) {
            inputStream.close();
        }
        //关闭通道
        sChannel.close();
        LOGGER.info("通道已关闭");
        return;
    }
    static void handleRead1(SelectionKey key) throws IOException {
        LOGGER.info("读取就绪！");
        //10. 获取 读状态 准备就绪的 selectionKey
        SocketChannel sChannel = (SocketChannel) key.channel();
        //创建缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1032);
        byte[] bytes = new byte[1024];

        int read = 0;
        StringBuilder receiveMessage = new StringBuilder();
        int temp;
        boolean statusSent = false;

        //从通道中循环读取数据写入缓冲区，直到达到流的末尾，也就是客户端主动关闭输出流（SocketChannel.shutdownOutput()），或者关闭通道。但由于稍后客户端还需要从服务端接收数据，因此客户端还不能关闭通道。
        while ((read = sChannel.read(buf)) != -1) {
            //该方法会让缓冲区切换到读取状态，即①ByteBuff.limit = ByteBuff.position; ②ByteBuff.position = 0;
            buf.flip();
            receiveMessage.append(StandardCharsets.UTF_8.newDecoder().decode(buf));
            //该方法让缓冲区清空，并准备下一次写入，即①ByteBuff.limit = ByteBuff.capacity; ②ByteBuff.position = 0;
            buf.clear();
        }
        //执行至此时，地址接收完毕
        String tpUrl = receiveMessage.toString();
        LOGGER.info("长度：[{}]，完整url: [{}]", tpUrl.length(), tpUrl);

        URL url = new URL(tpUrl);
        InputStream inputStream = null;
        int responseCode = -1;
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setConnectTimeout(10000);
        try {
            inputStream = con.getInputStream();
            responseCode = con.getResponseCode();
           // inputStreamToFile(inputStream,"5.png");
        } catch (IOException e) {
            String format = String.format("图片资源不存在或网络不通！图片地址：%s，异常信息：%s，状态码：%d", tpUrl, e.getMessage(), responseCode);
            e.printStackTrace();
            LOGGER.error(format);
            //返回异常信息。我在这里自定义了服务器返回的第一个Int类型数据标识着当前请求成功与否1：成功，0：失败。
            buf.putInt(0);
            buf.put(format.getBytes(StandardCharsets.UTF_8));
            buf.flip();
            sChannel.write(buf);
            buf.clear();
            if (inputStream != null) {
                inputStream.close();
            }
            //关闭通道，告知客户端写入已达到末尾，让它不再需要等待服务端。
            sChannel.close();
            return;
        }
        File file = new File("D:\\data\\aa\\7.png");
        if (file.exists()){
            file.createNewFile();
        }  OutputStream os = new FileOutputStream(file);
        while ((temp = inputStream.read(bytes)) != -1) {
            if (!statusSent) {
                //发送成功标识
                buf.putInt(1);
                statusSent = true;
            }
            byte[] wBytes = new byte[temp];
            System.arraycopy(bytes, 0, wBytes, 0, temp);
            os.write(wBytes);





            //写入图片数据到缓冲区
            buf.put(bytes, 0, temp);
            //反转一下，准备读取
            buf.flip();
            sChannel.write(buf);
            //清空缓冲区，准备下一次写入
            buf.clear();
        }
        os.flush();
        os.close();
        LOGGER.info("返回结束.");
        if (inputStream != null) {
            inputStream.close();
        }
        //关闭通道
        sChannel.close();
        LOGGER.info("通道已关闭");
        return;
    }
    /**
     * 流转换成文件
     * @param inputStream
     */
    public static void inputStreamToFile(InputStream inputStream, String filename){
        try{
            //新建文件
            File file = new File("D:\\data\\aa\\"+filename);
            if (file.exists()){
                file.createNewFile();
            }
            OutputStream os = new FileOutputStream(file);
            int read = 0;
            byte[] bytes = new byte[1024 * 1024];
            //先读后写
            while ((read = inputStream.read(bytes)) > 0){
                byte[] wBytes = new byte[read];
                System.arraycopy(bytes, 0, wBytes, 0, read);
                os.write(wBytes);
            }
            os.flush();
            os.close();
          //  inputStream.close();
        }catch (Exception e){
            e.printStackTrace();
        }

    }}


